package lumis.util.converter;

import java.sql.Time;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;

import lumis.util.log.ILogger;
import lumis.util.log.LoggerFactory;

/**
 * This converter is able to do conversion between {@link Date} and its
 * java.sql subtypes, String and Long. Long is treated as UTC.
 * <p>
 * By default this converter uses the jvm date time formats,
 * if pattern is not specified. 
 * Use {@link TimeConverter} or {@link DateConverter} to default to
 * jvm date or time formats.
 * <p>
 * This class accepts patterns specified in {@link SimpleDateFormat} or
 * the string &quot;ISO8601&quot; for ISO-8601 format pattern.
 *
 * @see java.util.Date
 * @version $Revision: 6376 $ $Date: 2007-04-10 17:55:53 -0300 (Tue, 10 Apr 2007) $
 * @since 4.0.4
 */
public class DateTimeConverter extends AbstractConverter
{
	private static ILogger logger = LoggerFactory.getLogger(DateTimeConverter.class);
	public static final String PATTERN_ISO_8601 = "ISO8601";
	
	/**
	 * Implementation of DateFormat for ISO-8601 parsing and formatting.
	 *
	 * @version $Revision: 6376 $ $Date: 2007-04-10 17:55:53 -0300 (Tue, 10 Apr 2007) $
	 * @since 4.0.10
	 */
	@SuppressWarnings("serial")
	protected static class ISO8601DateTimeFormat extends DateFormat
	{
		protected DateTimeFormatter getFormatter()
		{
			return org.joda.time.format.ISODateTimeFormat.dateTime();
		}
		
		protected DateTimeFormatter getParser()
		{
			return org.joda.time.format.ISODateTimeFormat.dateTimeParser();
		}
		
		@Override
		public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition)
		{
			DateTimeFormatter dtf = getFormatter();
			dtf.printTo(toAppendTo, date.getTime());
			return toAppendTo;
		}

		@Override
		public Date parse(String source, ParsePosition pos)
		{
			DateTimeFormatter dtf = getParser(); 
			int startIndex = pos.getIndex();
			DateTime parseDateTime = dtf.parseDateTime(source.substring(startIndex));
			pos.setIndex(startIndex + source.length());
			return new Date(parseDateTime.getMillis());
		}
	}

	/**
	 * Creates the DateFormat for the given locale and pattern.
	 * @param locale the locale.
	 * @param pattern the pattern.
	 * @return the DateFormat created.
	 * @see DateFormat
	 * @since 4.0.4
	 */
	protected DateFormat createDateFormat(Locale locale, String pattern)
	{
		if (PATTERN_ISO_8601.equals(pattern))
			return new ISO8601DateTimeFormat();
		
		if (pattern == null)
		{
			if (locale != null)
			{
	        	logger.debug("No pattern provided, using jvm default.");
				return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale);
			}
			else
			{
	        	logger.debug("No locale or pattern provided, using jvm default.");
				return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);					
			}
		}
		else
		{
			if (locale != null)
				return new SimpleDateFormat(pattern, locale);				
			else
			{
	        	logger.debug("No locale provided, using jvm default.");
				return new SimpleDateFormat(pattern);
			}
		}			
	}
	
	@SuppressWarnings("unchecked")
	@Override
	protected <T> T doConversion(Class<T> type, Object value, Locale locale,
			String pattern) throws UnsupportedConversionTypeException
	{
		// convert value to a Date
		Date dateValue;
		if (value instanceof Date)
			dateValue = (Date)value;
		else if (value instanceof Number)
		{
			dateValue = new Date(((Number)value).longValue());
		}
		else
		{
	        try
			{
	        	DateFormat formatter = createDateFormat(locale, pattern);
				dateValue = formatter.parse(value.toString());
			}
			catch (ParseException e)
			{
				throw new ConversionException(e);
			}
		}
		
		if (Date.class.equals(type))
		{
			return (T)dateValue;
		}
		else if (Time.class.equals(type))
		{
			return (T)new Time(dateValue.getTime());
		}
		else if (Timestamp.class.equals(type))
		{
			return (T)new Timestamp(dateValue.getTime());
		}
		else if (java.sql.Date.class.equals(type))
		{
			return (T)new java.sql.Date(dateValue.getTime());
		}
		else if (type.isAssignableFrom(String.class))
		{
			// convert the date to string using a date format and return it
        	DateFormat formatter = createDateFormat(locale, pattern);
        	return (T)formatter.format(dateValue);
		}
		else if (Long.class.isAssignableFrom(type))
		{
			return (T)new Long(dateValue.getTime());
		}
		else
			throw new UnsupportedConversionTypeException(type.getName());
	}
}
